package cn.itcast.nems.journal.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.approval.bpm.eventstrategy.BpmEventStrategy;
import cn.itcast.nems.approval.dao.entity.ApprovalConfig;
import cn.itcast.nems.approval.dao.entity.ApprovalForm;
import cn.itcast.nems.approval.enumeration.ApprovalErrorBodyEnum;
import cn.itcast.nems.approval.enumeration.ApprovalFormStateEnum;
import cn.itcast.nems.approval.service.ApprovalConfigService;
import cn.itcast.nems.approval.service.ApprovalFormFlowService;
import cn.itcast.nems.approval.service.ApprovalFormService;
import cn.itcast.nems.base.api.ClazzApi;
import cn.itcast.nems.base.dto.ClazzDTO;
import cn.itcast.nems.base.service.DictionaryService;
import cn.itcast.nems.common.PageQueryModel;
import cn.itcast.nems.common.account.AccountHolder;
import cn.itcast.nems.common.constant.OriginClazzOutTypeEnum;
import cn.itcast.nems.common.enumeration.BizNumberTypeEnum;
import cn.itcast.nems.common.enumeration.NemsErrorBodyEnum;
import cn.itcast.nems.common.service.AfterTransactionCommitExecutor;
import cn.itcast.nems.common.service.BizNumberService;
import cn.itcast.nems.common.util.NumberUtil;
import cn.itcast.nems.common.utils.WrapperUtil;
import cn.itcast.nems.journal.dto.JournalClazzDTO;
import cn.itcast.nems.journal.dto.JournalRepeatModel;
import cn.itcast.nems.journal.enumeration.JournalStateEnum;
import cn.itcast.nems.journal.service.JournalRepeatService;
import cn.itcast.nems.journal.service.JournalTransferLineService;
import cn.itcast.nems.journal.util.JournalRepeatUtil;
import cn.itcast.nems.journal.util.JournalUtil;
import cn.itcast.nems.manager.activity.BpmWorkflowManager;
import cn.itcast.nems.manager.util.CustomBeanUtil;
import cn.itcast.nems.oldems.dto.StudentRecordCirculation;
import cn.itcast.nems.oldems.dto.oldapi.EmsApiResult;
import cn.itcast.nems.oldems.service.sync.OldEmsSyncService;
import cn.itcast.nems.product.scheduler.TuitionFeeService;
import cn.itcast.nems.studentrecord.api.JournalRepeatApi;
import cn.itcast.nems.studentrecord.api.StudentRecordApi;
import cn.itcast.nems.studentrecord.bo.JournalClazzModel;
import cn.itcast.nems.studentrecord.constant.ReferenceTypeEnum;
import cn.itcast.nems.studentrecord.dao.condition.ApprovalType;
import cn.itcast.nems.studentrecord.dao.dataobject.JournalRepeatDO;
import cn.itcast.nems.studentrecord.dao.entity.JournalRepeat;
import cn.itcast.nems.studentrecord.dao.entity.JournalTransferLine;
import cn.itcast.nems.studentrecord.dao.mapper.JournalRepeatMapper;
import cn.itcast.nems.studentrecord.dto.StudentRecordDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeResultDTO;
import cn.itcast.nems.studentrecord.dto.repeat.JournalRepeatDTO;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordErrorBodyEnum;
import cn.itcast.nems.studentrecord.service.StudentRecordRepeatService;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordService;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordStudentRelationshipService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

/**
 * <p>
 * 重读凭单 服务实现类
 * </p>
 *
 * @author 刘曙
 * @since 2023-12-12
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class JournalRepeatServiceImpl extends ServiceImpl<JournalRepeatMapper, JournalRepeat>
        implements JournalRepeatService, JournalRepeatApi, BpmEventStrategy {
    private final static List<String> forbidRepeatReason  = Arrays.asList(ReferenceTypeEnum.DROPOUT.name(),ReferenceTypeEnum.FINAL.name()) ;

    private final ApprovalFormService approvalFormService;
    private final ApprovalFormFlowService approvalFormFlowService;
    private final ClazzApi clazzApi ;
    private final BizNumberService bizNumberService ;
    private final StudentRecordApi studentRecordApi ;
    private final ApprovalConfigService approvalConfigService;
    private final StudentRecordService studentRecordService ;
    private final StudentRecordRepeatService studentRecordRepeatService ;
    private  final StudentRecordStudentRelationshipService studentRecordStudentRelationshipService ;
    private final TuitionFeeService tuitionFeeService ;
    private final DictionaryService dictionaryService ;
    private final BpmWorkflowManager bpmWorkflowManager;
    private final OldEmsSyncService oldEmsSyncService ;
    private final AfterTransactionCommitExecutor executor;

    @Override
    public String getStrategyName() {
        return ApprovalFormService.JournalType.REPEAT.name();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inform(String formId, String targetAssignee) {
        approvalFormFlowService.inform(formId, targetAssignee);
    }
    @Override
    public cn.itcast.ic.common.bean.Page<JournalClazzDTO> findClazzList4PageSearch(PageQueryModel<JournalClazzModel> queryModel) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<JournalRepeatDO> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>();
        page.setCurrent(queryModel.getPageNumber());
        page.setSize(queryModel.getPageSize());
        JournalClazzModel clazzModel = Objects.nonNull(queryModel.getCondition()) ? queryModel.getCondition() : new JournalClazzModel();
        JournalRepeatDO repeatDO = new JournalRepeatDO();
        repeatDO.setTargetClazzName(clazzModel.getClazzName());
        JournalUtil.invokeByApprovalType(this.getJournalIdFormIds(), repeatDO, ApprovalFormService.JournalType.REPEAT.name(),clazzModel.getApprovalType());
        ApprovalType approvalType = repeatDO.getApprovalType();
        if (approvalType == ApprovalType.ALL) {
            if (!AccountHolder.hasEmployeeGroups()) {
                return cn.itcast.ic.common.bean.Page.of(queryModel.getPageNumber(), queryModel.getPageSize(), 0, null);
            }
            repeatDO.setEmployeeGroups(AccountHolder.getEmployeeGroups());
        }
        IPage<JournalRepeatDO> pageData = getBaseMapper().findClazzPageList(page, repeatDO);
        List<JournalClazzDTO> list = CollectionUtil.isNotEmpty(pageData.getRecords()) ?
                pageData.getRecords().stream().map(item -> new JournalClazzDTO(item.getTargetClazzId(), item.getTargetClazzName())).toList() : null;
        return cn.itcast.ic.common.bean.Page.of(queryModel.getPageNumber(), queryModel.getPageSize(), Long.valueOf(pageData.getTotal()).intValue(), list);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Runnable finish(String formId) {
        ApprovalForm approvalForm = approvalFormService.getByBusinessKey(formId);
        String journalId = approvalForm.getJournalId();
        log.info("【审批完成】查询审批单详情：{}", JSON.toJSONString(approvalForm));
        // 处理凭单自己的业务逻辑
        final JournalRepeat journalRepeat  = getBaseMapper().selectById(journalId) ;
        log.info("【审批完成】查询重读凭单详情：{}", JSON.toJSONString(journalRepeat));
        Integer studentId = studentRecordStudentRelationshipService.findStudentId(journalRepeat.getOriginStudentRecordId()) ;
        ClazzDTO clazzDTO  = clazzApi.queryByClassId(journalRepeat.getTargetClazzId())  ;
        Assert.notNull(clazzDTO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "班级ID：" +  journalRepeat.getTargetClazzId())) ;
        StudentRecordDTO  originStudentRecord = studentRecordApi.queryById(journalRepeat.getOriginStudentRecordId()) ;
        /*
         * 重读原学籍为“休学班级”且 对应班级为 【休学】, -- 需要排除休学归来的情况
         *
         * 1、检查“休学班级”的学籍是否存在休学凭单 ，  如果有取该凭单原学籍对应的班级
         * 2、不存在休学凭单 ，  取当前学籍对应班级
         */
        if (ReferenceTypeEnum.SUSPENSION.name().equals(originStudentRecord.getSource())) {
            ClazzDTO originClazzDTO = clazzApi.queryByClassId(originStudentRecord.getClazzId());
            Assert.notNull(originClazzDTO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "【审批完成】重读凭单原学籍班级ID：" + originStudentRecord.getClazzId()));
            log.info("【审批完成】重读凭单原学籍来源：{}，是否为休学班级：{}", originStudentRecord.getSource(), originClazzDTO.getSuspensionFlag());
            if (StringUtils.hasText(originStudentRecord.getAssociatedRecordId())) {
                StudentRecordDTO suspensionOriginStudentRecord = studentRecordApi.queryById(originStudentRecord.getAssociatedRecordId());
                Assert.notNull(suspensionOriginStudentRecord, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "休学原学籍ID：" + suspensionOriginStudentRecord.getId()));
                log.info("当前学籍ID：{}，班级ID：{},休学原学籍ID：{}, 原学籍对应班级ID：{}",
                        originStudentRecord.getId(), originStudentRecord.getClazzId(),
                        suspensionOriginStudentRecord.getId(), suspensionOriginStudentRecord.getClazzId());
                originStudentRecord.setClazzId(suspensionOriginStudentRecord.getClazzId());
            }
        }
        // 将升级凭单学籍信息同步至老EMS
        final StudentRecordCirculation circulation = JournalRepeatUtil.wrapCirculation(journalRepeat, originStudentRecord, clazzDTO, studentId);
        log.info("【审批完成】重读凭单同步至老EMS参数：{}" ,JSON.toJSONString(circulation , SerializerFeature.DisableCircularReferenceDetect));
        final EmsApiResult apiResult = oldEmsSyncService.circulations(List.of(circulation));
        log.info("【审批完成】重读凭单同步至老EMS响应正文：{}", apiResult);
        StudentRecordChangeResultDTO studentRecordChangeResultDTO = studentRecordRepeatService.repeat(this.transferToStudentRecordChangeDTO(journalRepeat, studentId)) ;
        log.info("【审批完成】调用转班生学籍参数：{}", JSON.toJSONString(studentRecordChangeResultDTO) );
        Assert.notNull(studentRecordChangeResultDTO ,BizExceptionProducer.serverError("处理重读申请结果为空")) ;

        getBaseMapper().update(null,
                new LambdaUpdateWrapper<JournalRepeat>()
                        .eq(JournalRepeat::getId, journalId)
                        .set(JournalRepeat::getState, ApprovalFormStateEnum.FINISHED.name())
                        .set(JournalRepeat::getOrderId,studentRecordChangeResultDTO.getOrderId())
                        .set(JournalRepeat::getStudentRecordId,studentRecordChangeResultDTO.getTargetStudentRecordId()));
        log.info("【审批完成】修改重读凭单状态 ID：{}， 修改状态：{}", journalId, ApprovalFormStateEnum.FINISHED.name());
        // 处理本地的审批流程单
        approvalFormService.finish(formId);
        log.info("【重读凭单审批】审批完成，处理结束 formID：{} , journalId：{}", formId, journalId);
        return  ()->  updateStudentRecordOldRecordId(journalId);
    }

    private void updateStudentRecordOldRecordId (String journalId) {
        JournalRepeat journalRepeat = getBaseMapper().selectById(journalId);
        Collection<String> studentRecordIds = Collections.singleton(journalRepeat.getStudentRecordId()) ;
        log.info("【重读凭单-审批完成】维护老EMS学籍：{}" ,JSON.toJSONString(studentRecordIds));
        studentRecordService.syncSjc(studentRecordIds);
    }

    private StudentRecordChangeDTO transferToStudentRecordChangeDTO(JournalRepeat journalRepeat ,Integer studentId){
        StudentRecordChangeDTO studentRecordChangeDTO =new StudentRecordChangeDTO() ;
        studentRecordChangeDTO.setOriginStudentRecordId(journalRepeat.getOriginStudentRecordId());
        studentRecordChangeDTO.setTargetClazzPrice(journalRepeat.getTargetClazzPrice());
        studentRecordChangeDTO.setTargetClazzAmountDue(journalRepeat.getAmount());
        studentRecordChangeDTO.setTargetClazzActualAmountDue(journalRepeat.getAmount());
        studentRecordChangeDTO.setTargetClazzTransferAmount(0);
        studentRecordChangeDTO.setTargetClazzId(journalRepeat.getTargetClazzId());
        studentRecordChangeDTO.setTargetClazzDiscountAmount(0);
        studentRecordChangeDTO.setRemark(journalRepeat.getRemark());
        studentRecordChangeDTO.setJournalId(journalRepeat.getId());
        studentRecordChangeDTO.setJournalLineId(journalRepeat.getId());
        studentRecordChangeDTO.setStudentId(studentId);
        studentRecordChangeDTO.setJournalType(ReferenceTypeEnum.REPEAT);
        studentRecordChangeDTO.setOriginClazzOutDate(journalRepeat.getOriginClazzOutDate());
        studentRecordChangeDTO.setTargetClazzInDate(journalRepeat.getTargetClazzInDate());
        studentRecordChangeDTO.setTargetClazzName(journalRepeat.getTargetClazzName());
        studentRecordChangeDTO.setOriginClazzId(journalRepeat.getOriginClazzId());
        studentRecordChangeDTO.setOriginClazzName(journalRepeat.getOriginClazzName());
        return studentRecordChangeDTO ;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approve(String formId, ApprovalFormService.ApproveResult result, String comment, final String form, Integer applyType) {
        ApprovalForm approvalForm = approvalFormService.getById(formId);
        JournalRepeat journalRepeat = getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(formId, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "重读凭单ID：" + approvalForm.getJournalId()));
        Assert.isTrue(ApprovalFormStateEnum.PENDING.name().equalsIgnoreCase(journalRepeat.getState())
                        || ApprovalFormStateEnum.APPROVING.name().equalsIgnoreCase(journalRepeat.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_NO_PENDING_APPROVING));
        if (Objects.requireNonNull(result) == ApprovalFormService.ApproveResult.REJECT) {
            getBaseMapper().update(null,
                    new LambdaUpdateWrapper<JournalRepeat>().eq(JournalRepeat::getId, approvalForm.getJournalId())
                            .set(JournalRepeat::getState, ApprovalFormStateEnum.REJECTED));
            this.studentRecordService.unlocks(Set.of(journalRepeat.getOriginStudentRecordId()), ReferenceTypeEnum.REPEAT, approvalForm.getJournalId());

            JournalRepeat updateEntity = new JournalRepeat();
            updateEntity.setId(formId);
            updateEntity.setState(ApprovalFormStateEnum.REJECTED.name());
            super.updateById(updateEntity);
        } else {
            JournalStateEnum journalStateEnum = JournalStateEnum.APPROVING;
            //  1：审批（默认））2：驳回到发起人）3：驳回上一级）
            if (applyType != null && applyType == 2) {
                journalStateEnum = JournalStateEnum.DRAFT;
                this.studentRecordService.unlocks(Set.of(journalRepeat.getOriginStudentRecordId()), ReferenceTypeEnum.REPEAT, approvalForm.getJournalId());
            } else if (applyType != null && applyType == 3) {
                journalStateEnum = JournalStateEnum.PENDING;
            }
            getBaseMapper().update(null,
                    new LambdaUpdateWrapper<JournalRepeat>().eq(JournalRepeat::getId, journalRepeat.getId())
                            .set(JournalRepeat::getState, journalStateEnum.name()));
        }
        // 处理审批单的业务逻辑
        approvalFormService.approve(formId, result, comment, form, applyType);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(String formId) {
        ApprovalForm approvalForm = approvalFormService.getById(formId);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单ID：" + formId));
        Assert.isTrue(approvalForm.getCreatedBy().equalsIgnoreCase(AccountHolder.getRequiredAccount()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_CANCEL_NO_CREATED_BY));

        JournalRepeat journalRepeat = getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(journalRepeat, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单ID：" + approvalForm.getJournalId()));
        Assert.isTrue(ApprovalFormStateEnum.PENDING.name().equalsIgnoreCase(journalRepeat.getState()), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_CANCEL_NO_PENDING));
        getBaseMapper().update(null,
                new LambdaUpdateWrapper<JournalRepeat>().eq(JournalRepeat::getId, approvalForm.getJournalId())
                        .set(JournalRepeat::getState, JournalStateEnum.DRAFT.name()));

        // 解锁学籍
        log.info("【撤销流程】查询转出班学籍ID：{}", JSON.toJSONString(journalRepeat.getOriginStudentRecordId()));
        studentRecordService.unlocks(Set.of(journalRepeat.getOriginStudentRecordId()),  ReferenceTypeEnum.REPEAT , approvalForm.getJournalId());
        log.info("【撤销流程】解锁学籍ID列表：{}", JSON.toJSONString(journalRepeat.getOriginStudentRecordId()));
        approvalFormService.cancel(formId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollbackRequester(String formId) {
        ApprovalForm approvalForm = approvalFormService.getById(formId);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单ID：" + formId));

        JournalRepeat journalRepeat = getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(journalRepeat, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单ID：" + approvalForm.getJournalId()));
        if (journalRepeat.getState().equals(JournalStateEnum.DRAFT.name())) {
            return;
        }
        getBaseMapper().update(null,
                new LambdaUpdateWrapper<JournalRepeat>().eq(JournalRepeat::getId, approvalForm.getJournalId())
                        .set(JournalRepeat::getState, JournalStateEnum.DRAFT.name()));

        // 解锁学籍
        log.info("【撤销流程】查询转出班学籍ID：{}", JSON.toJSONString(journalRepeat.getOriginStudentRecordId()));
        studentRecordService.unlocks(Set.of(journalRepeat.getOriginStudentRecordId()),  ReferenceTypeEnum.REPEAT , approvalForm.getJournalId());
        log.info("【撤销流程】解锁学籍ID列表：{}", JSON.toJSONString(journalRepeat.getOriginStudentRecordId()));
//        approvalFormService.cancel(formId);
    }

    @Override
    public void remind(String formId, String targetAssignee) {
        String result = approvalFormService.remind(formId, targetAssignee);
        log.info("【调用工作流催办接口】响应正文：" + result);
    }
    @Override
    public IPage<JournalRepeatDO> findPageList(PageQueryModel<JournalRepeatModel> queryModel) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<JournalRepeatDO> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>();
        page.setCurrent(queryModel.getPageNumber());
        page.setSize(queryModel.getPageSize());
        JournalRepeatModel repeatModel = Objects.nonNull(queryModel.getCondition()) ? queryModel.getCondition() : new JournalRepeatModel();
        JournalRepeatDO repeatDO = CustomBeanUtil.copyProperties(repeatModel, new JournalRepeatDO());
        JournalUtil.invokeByApprovalType(this.getJournalIdFormIds(), repeatDO, ApprovalFormService.JournalType.REPEAT.name(),repeatModel.getApprovalType());
        ApprovalType approvalType = repeatDO.getApprovalType();
        if (approvalType == ApprovalType.ALL) {
            if (!AccountHolder.hasEmployeeGroups()) {
                return page;
            }
            repeatDO.setEmployeeGroups(AccountHolder.getEmployeeGroups());
        }
        return getBaseMapper().findPageList(page, repeatDO);
    }

    private List<String> getJournalIdFormIds() {
        ApprovalConfig config = approvalConfigService.getByJournalCode(ApprovalFormService.JournalType.REPEAT.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, ApprovalFormService.JournalType.REPEAT.getCnName()));
        return bpmWorkflowManager.findWaitApproval(config.getBpmProcessKey(), AccountHolder.getRequiredAccount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String create(JournalRepeatDTO repeatDTO) {
        this.validateCreateParam(repeatDTO);
        JournalRepeat journalRepeat = this.wrapJournalRepeatEntity(repeatDTO);
        journalRepeat.setState(JournalStateEnum.DRAFT.name());
        this.saveOrUpdate(journalRepeat);
        return journalRepeat.getId();
    }

    @Override
    public JournalRepeatDTO findById(String id) {
        JournalRepeatDO journalRepeat = getBaseMapper().findById(id) ;
        Assert.notNull(journalRepeat, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "重读凭单ID："+id)) ;
        JournalRepeatDTO journalRepeatDTO  =CustomBeanUtil.copyProperties(journalRepeat , new JournalRepeatDTO()) ;
        this.invokeClassInfo(journalRepeatDTO) ;
        Integer studentId = studentRecordStudentRelationshipService.findStudentId(journalRepeat.getOriginStudentRecordId()) ;
        journalRepeatDTO.setStudentId(studentId);
        // 原班级是否可升级
        ClazzDTO clazzDTO = clazzApi.queryByClassId(journalRepeat.getOriginClazzId()) ;
        Assert.notNull(clazzDTO   , BizExceptionProducer.produce( NemsErrorBodyEnum.DATA_EXIST, "转出班级ID：" + journalRepeatDTO.getOriginClazzId())) ;
        journalRepeatDTO.setUpgradeable(clazzDTO.getUpgradeable());
        //  获取审批单ID created_datetime
        ApprovalForm approvalForm = approvalFormService.getOne(
                new WrapperUtil<ApprovalForm>().limitLaseOne("created_datetime").eq(ApprovalForm::getJournalId, id)
                .eq(ApprovalForm::getDeletedFlag, false)
                .notIn(ApprovalForm::getState, ApprovalFormStateEnum.CANCELED.name()));
        journalRepeatDTO.setFormId(Objects.nonNull(approvalForm) ? approvalForm.getId() : null);
        return journalRepeatDTO;
    }
    // 同四项
    private void invokeClassInfo(JournalRepeatDTO journalRepeatDTO) {
        ClazzDTO originClazzDO = clazzApi.queryByClassId(journalRepeatDTO.getOriginClazzId()) ;
        Assert.notNull(originClazzDO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"转出班级ID："+ journalRepeatDTO.getOriginClazzId())) ;
        log.info("【重读凭单-查询详情】获取转出班级详情：{}" ,JSON.toJSONString(originClazzDO));
        journalRepeatDTO.setSchoolId(originClazzDO.getSchoolId());
        journalRepeatDTO.setSchoolName(originClazzDO.getSchoolName());
        journalRepeatDTO.setModelId(originClazzDO.getModelId());
        journalRepeatDTO.setModelName(originClazzDO.getModelName());
        journalRepeatDTO.setSubjectId(originClazzDO.getSubjectId());
        journalRepeatDTO.setSubjectName(originClazzDO.getSubjectName());
        journalRepeatDTO.setClassTypeId(originClazzDO.getClassTypeId());
        journalRepeatDTO.setClassTypeName(originClazzDO.getClassTypeName());
        journalRepeatDTO.setOriginPredictionGraduationDate(originClazzDO.getPredictionGraduationDate());
        ClazzDTO targetClazzDO = clazzApi.queryByClassId(journalRepeatDTO.getTargetClazzId()) ;
        Assert.notNull(targetClazzDO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"转出班级ID："+ journalRepeatDTO.getTargetClazzId())) ;
        log.info("【重读凭单-查询详情】获取重读班级详情：{}" ,JSON.toJSONString(originClazzDO));
        journalRepeatDTO.setTargetPredictionGraduationDate(targetClazzDO.getPredictionGraduationDate());

        // 原班级学籍的进班时间
        StudentRecordDTO recordDTO = studentRecordApi.queryById(journalRepeatDTO.getOriginStudentRecordId()) ;
        Assert.notNull(recordDTO ,  BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "原班级学籍ID：" + journalRepeatDTO.getOriginStudentRecordId())) ;
        journalRepeatDTO.setOriginStudentRecordInDate(recordDTO.getInDate());
        // 转出班的开班时间
        journalRepeatDTO.setOriginClazzStartDate(originClazzDO.getStartDate());
        // 转入班开班时间
        journalRepeatDTO.setTargetClazzStartDate(targetClazzDO.getStartDate());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String flow(JournalRepeatDTO repeatDTO) {
        this.validateCreateParam(repeatDTO);
        JournalRepeat journalRepeat = this.wrapJournalRepeatEntity(repeatDTO);
        journalRepeat.setState(JournalStateEnum.PENDING.name());
        this.saveOrUpdate(journalRepeat);
        ClazzDTO clazzDTO = clazzApi.queryByClassId(repeatDTO.getTargetClazzId()) ;
        Assert.notNull(clazzDTO ,BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"班级ID："+repeatDTO.getTargetClazzId())) ;
        HashMap<String, Object> variables = JournalRepeatUtil.getVariables( repeatDTO.getSpecialFlag(),clazzDTO.getSchoolId());
        ApprovalConfig config = approvalConfigService.getByJournalCode(ApprovalFormService.JournalType.REPEAT.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, ApprovalFormService.JournalType.REPEAT.getCnName()));
        this.lockOriginStudentRecord(journalRepeat.getOriginStudentRecordId() ,journalRepeat.getId());
        approvalFormService.start(ApprovalFormService.JournalType.REPEAT, journalRepeat.getId(), variables, config.getBpmProcessKey());
        repeatDTO.setId(journalRepeat.getId());
        return journalRepeat.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void flow(String id) {
        JournalRepeat journalRepeat =  getBaseMapper().selectById(id) ;
        Assert.notNull(journalRepeat, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "重读凭单ID："+id)) ;
        JournalRepeatDTO journalRepeatDTO  = CustomBeanUtil.copyProperties(journalRepeat , new JournalRepeatDTO()) ;
        this.validateCreateParam(journalRepeatDTO) ;
        getBaseMapper().update(null ,
                                            new LambdaUpdateWrapper<JournalRepeat>()
                                                    .eq(JournalRepeat::getId, id)
                                                    .set(JournalRepeat::getState,JournalStateEnum.PENDING.name() )) ;
        ApprovalConfig config = approvalConfigService.getByJournalCode(ApprovalFormService.JournalType.REPEAT.name());
        ClazzDTO clazzDTO = clazzApi.queryByClassId(journalRepeat.getTargetClazzId()) ;
        Assert.notNull(clazzDTO ,BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"班级ID："+journalRepeat.getTargetClazzId())) ;
        HashMap<String, Object> variables = JournalRepeatUtil.getVariables( journalRepeat.getSpecialFlag(),clazzDTO.getSchoolId());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, ApprovalFormService.JournalType.REPEAT.getCnName()));
        this.lockOriginStudentRecord(journalRepeat.getOriginStudentRecordId() ,journalRepeat.getId());
        approvalFormService.start(ApprovalFormService.JournalType.REPEAT, id, variables, config.getBpmProcessKey());
    }

    /**
     * 校验学籍ID是否为空、切数据库存在
     *
     * @param recordId 基础班学籍ID
     */
    private void validateStudentRecord( String recordId) {
        Assert.notNull(recordId, BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "转出班级学籍ID"));
        StudentRecordDTO studentRecord = studentRecordApi.queryById(recordId) ;
        Assert.notNull(studentRecord , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"学籍ID：" +  recordId )) ;
        Assert.isTrue(studentRecord.getLockFlag()==0, BizExceptionProducer.produce(StudentRecordErrorBodyEnum.STUDENT_RECORD_LOCKED, "学籍ID：" + recordId ));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(String id) {
        JournalRepeat journalRepeat = getBaseMapper().selectById(id);
        Assert.notNull(journalRepeat, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "ID：" + id));
        Assert.isTrue(AccountHolder.getRequiredAccount().equalsIgnoreCase(journalRepeat.getCreatedBy()) , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_DELETE_UNAUTHORIZED));
        Assert.isTrue(JournalStateEnum.DRAFT.name().equalsIgnoreCase(journalRepeat.getState()), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_NO_DRAFT_DELETE));
        Assert.isTrue(getBaseMapper().update(null, new LambdaUpdateWrapper<JournalRepeat>().eq(JournalRepeat::getId, id).set(JournalRepeat::getDeletedFlag, true)) > 0,
                BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR, id));
    }

    /**
     * 将重读凭单-行对应的学籍标识为 ”上锁“状态
     * @param originStudentRecordId 转出班级学籍ID
     */
    private void lockOriginStudentRecord(@NotNull String originStudentRecordId , String journalId ) {
            this.validateStudentRecord(originStudentRecordId );
            studentRecordService.locks(Set.of(originStudentRecordId), ReferenceTypeEnum.REPEAT , journalId);
    }

    @Override
    public void testFinish(String formId) {
        this.finish(formId);
    }

    @Override
    public JournalRepeatDTO findByOrderId(String orderId){
        JournalRepeat journalRepeat = getBaseMapper().selectByOrderId(orderId);
        JournalRepeatDTO result = new JournalRepeatDTO();
        BeanUtils.copyProperties(journalRepeat, result);
        return result;
    }

    @Override
    public JournalRepeatDTO findByTargetStudentRecordId(String targetStudentRecordId){
        JournalRepeat journalRepeat = getBaseMapper().selectByTargetStudentRecordId(targetStudentRecordId);
        JournalRepeatDTO result = new JournalRepeatDTO();
        BeanUtils.copyProperties(journalRepeat, result);
        return result;
    }

    @Override
    public String getByOrderId(String orderId) {
        JournalRepeat journalRepeat = getBaseMapper().selectOne(new LambdaQueryWrapper<JournalRepeat>()
                .eq(JournalRepeat::getOrderId, orderId)
                .eq(JournalRepeat::getDeletedFlag, false));
        if (journalRepeat == null) {
            return null;
        }
        return journalRepeat.getId();
    }


    /**
  * <p>
  * 检查学籍转班原因：【转出班级】不可选择非【结课（结课凭单）】、【退学】；
  * </p>
  *
  * @param studentRecordId 学籍ID
  * @author zhouwei
  * @since 2023/12/13 1:49 PM
  **/
    private void checkStudentRecordSettlementReason(String studentRecordId) {
        StudentRecordDTO studentRecordDTO = studentRecordApi .queryById(studentRecordId) ;
        log.info("【重读凭单】检查学籍转班原因， 学籍详情：{}" ,JSON.toJSONString(studentRecordDTO));
        Assert.notNull(studentRecordDTO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "学籍ID："+  studentRecordDTO)) ;
        boolean flag =  Objects.nonNull(studentRecordDTO.getSettlementDate())   &&  forbidRepeatReason.contains(studentRecordDTO.getSettlementReason()) ;
        flag =  flag || Objects.isNull(studentRecordDTO.getSettlementDate()) ;
        log.info("【结课凭单】校验重读原因：{}" ,studentRecordDTO.getSettlementReason());
        String reasonName  =  Objects.nonNull(studentRecordDTO.getSettlementReason()) && ReferenceTypeEnum.names.contains(studentRecordDTO.getSettlementReason())?
                ReferenceTypeEnum.valueOf(studentRecordDTO.getSettlementReason()).getName() :
                "" ;
        Assert.isTrue(flag,BizExceptionProducer.produce(StudentRecordErrorBodyEnum.SETTLEMENT_REPEAT_CLAZZ_ERROR, reasonName));
    }

    /**
     * <p>
     * 创建重读凭单表实体
     * </p>
     *
     * @param repeatDTO 业务参数
     * @author zhouwei
     * @since 2023/12/13 7:36 PM
     **/
    private JournalRepeat wrapJournalRepeatEntity(JournalRepeatDTO repeatDTO ) {
        JournalRepeat  journalRepeat = null;
        if(Objects.nonNull(repeatDTO.getId())){
            journalRepeat = getBaseMapper().selectById(repeatDTO.getId());
        }
        journalRepeat =CustomBeanUtil.copyProperties(repeatDTO , Objects.requireNonNullElse(journalRepeat, new JournalRepeat())) ;
        journalRepeat.setBizDate(LocalDateTime.now());
        // 执行价格
        BigDecimal price = tuitionFeeService.findTuition(repeatDTO.getTargetClazzId(), null) ;
        journalRepeat.setTargetClazzPrice(NumberUtil.convertToCent(price));
        if(Objects.isNull(journalRepeat.getId())) {
            journalRepeat.setId(bizNumberService.next(BizNumberTypeEnum.JOURNAL_REPEAT));
        }
        return  journalRepeat    ;
    }

    /**
     * <p>
     * 验证保存草稿、提交审批页面参数
     * </p>
     *
     * @param repeatDTO 页面参数
     * @author zhouwei
     * @since 2023/12/13 7:36 PM
     **/
    private void validateCreateParam (JournalRepeatDTO repeatDTO ) {
        this.checkStudentRecordSettlementReason(repeatDTO.getOriginStudentRecordId())  ;
        if(Objects.nonNull(repeatDTO.getId())) {
            JournalRepeat journalRepeat  = getBaseMapper().selectById(repeatDTO.getId() );
            Assert.notNull(journalRepeat , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "重读凭单ID："+repeatDTO.getId())) ;
            Assert.isTrue(Objects.isNull(journalRepeat.getState()) || JournalStateEnum.DRAFT.name().equalsIgnoreCase(journalRepeat.getState()));
        }
        // 重读班级应付金额的规则调整，根据选择的［是否特殊审批］来判断需要支付的重读金额，即：选择［否］，重读金额默认［500元］；选择［是］，重读金额默认［0元］。不可以修改。
        switch (repeatDTO.getAmount()) {
            case 0 -> Assert.isTrue(repeatDTO.getSpecialFlag(),
                    BizExceptionProducer.badRequest("特殊审批选择【是】重读金额只能设置为0元！"));
            case 50000 -> Assert.isTrue(!repeatDTO.getSpecialFlag(),
                    BizExceptionProducer.badRequest("特殊审批选择【否】重读金额只能设置为500元！"));
            default -> Assert.isTrue(false, BizExceptionProducer.badRequest("重读金额只能输入0元或者500元！"));
        }
        // 重读原因
        Assert.notNull(repeatDTO.getReasonCode() ,BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY ,"重读原因")) ;
        // 出班情况
        Assert.notNull(repeatDTO.getOriginClazzOutType() ,BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY ,"出班情况")) ;
        ClazzDTO originClazzDO = clazzApi.queryByClassId(repeatDTO.getOriginClazzId()) ;
        Assert.notNull(originClazzDO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "转出班级ID："+  repeatDTO.getOriginClazzId()));
        repeatDTO.setOriginClazzName(originClazzDO.getClassName());
        ClazzDTO targetClazzDTO =   clazzApi.queryByClassId(repeatDTO.getTargetClazzId()) ;
        Assert.notNull(targetClazzDTO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "重读班级ID："+  repeatDTO.getTargetClazzId()));
        OriginClazzOutTypeEnum outSituationEnum = null ;
        // 重读原因CODE;来自字典表
        String reasonDescription = dictionaryService.getNameByParentCode("NEMS_JOURNAL_REPEAT_REASON",repeatDTO.getReasonCode()) ;
        Assert.notEmpty(reasonDescription , BizExceptionProducer.produce(NemsErrorBodyEnum.DICTIONARY_EXIST,repeatDTO.getReasonCode())) ;
        repeatDTO.setReasonDescription(reasonDescription);
        try {
            outSituationEnum =  OriginClazzOutTypeEnum.valueOf(repeatDTO.getOriginClazzOutType()) ;
        }catch (Exception e) {
            log.error("枚举转化失败,异常：{}", e.getMessage());
            BizExceptionProducer.throwProduce(NemsErrorBodyEnum.ENUM_ERROR);
        }
        /**
         * 出班时间 >= 预计结课时间 ，则为结课后重读
         * 出班时间<预计结课时间 ，为  结课前重读
         */
        switch (outSituationEnum) {
            case SETTLEMENT_AFTER ->
                    Assert.isFalse(repeatDTO.getOriginClazzOutDate().isBefore(originClazzDO.getPredictionGraduationDate()),
                            BizExceptionProducer.produce(StudentRecordErrorBodyEnum.SETTLEMENT_OUT_TYPE_ERROR,
                                    LocalDateTimeUtil.formatNormal(originClazzDO.getPredictionGraduationDate()),
                                    LocalDateTimeUtil.formatNormal(repeatDTO.getOriginClazzOutDate())));
            case SETTLEMENT_BEFORE ->
                    Assert.isTrue(repeatDTO.getOriginClazzOutDate().isBefore(originClazzDO.getPredictionGraduationDate()),
                            BizExceptionProducer.produce(StudentRecordErrorBodyEnum.SETTLEMENT_OUT_TYPE_ERROR,
                                    LocalDateTimeUtil.formatNormal(originClazzDO.getPredictionGraduationDate()),
                                    LocalDateTimeUtil.formatNormal(repeatDTO.getOriginClazzOutDate())));
        }
        repeatDTO.setTargetClazzName(targetClazzDTO.getClassName());
        Assert.isFalse(repeatDTO.getOriginClazzId().equals(repeatDTO.getTargetClazzId()), "转出班级和重读班级不能为相同班级，请重新选择！");

    }


}
